package database;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import configs.ServerConfig;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import server.console.Start;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * All OdinMS servers maintain a Database Connection. This class therefore
 * "singletonices" the connection per process.
 *
 * @author Frz
 */
public class DatabaseConnection {

    public static final int RETURN_GENERATED_KEYS = 1;
    public static final Logger log = LogManager.getLogger(DatabaseConnection.class);
    private static HikariDataSource ds;
    private static final long connectionTimeOut = 300000; // 5 minutes 300000毫秒

    public static void init() {
        String dbUrl = "jdbc:mysql://" + ServerConfig.DB_IP + ":" + ServerConfig.DB_PORT + "/" + ServerConfig.DB_NAME + "?characterEncoding=GBK&zeroDateTimeBehavior=convertToNull";
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setJdbcUrl(dbUrl);
        hikariConfig.setUsername(ServerConfig.DB_USER);
        hikariConfig.setPassword(ServerConfig.DB_PASSWORD);
        hikariConfig.setMaximumPoolSize(ServerConfig.DB_MAXPOOLSIZE);
        hikariConfig.addDataSourceProperty("cachePrepStmts", "true");
        hikariConfig.addDataSourceProperty("prepStmtCacheSize", "250");
        hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
        hikariConfig.addDataSourceProperty("useServerPrepStmts", true);
        hikariConfig.addDataSourceProperty("useLocalSessionState", true);
        hikariConfig.addDataSourceProperty("rewriteBatchedStatements ", true);
        hikariConfig.addDataSourceProperty("cacheResultSetMetadata", true);
        hikariConfig.addDataSourceProperty("cacheServerConfiguration ", true);
        hikariConfig.addDataSourceProperty("elideSetAutoCommits ", true);
        hikariConfig.addDataSourceProperty("maintainTimeStats ", true);
        hikariConfig.setLeakDetectionThreshold(30000);
        ds = new HikariDataSource(hikariConfig);
    }

    private static long getWaitTimeout(Connection con) {
        try (Statement stmt = con.createStatement()) {
            try (ResultSet rs = stmt.executeQuery("SHOW VARIABLES LIKE 'wait_timeout'")) {
                if (rs.next()) {
                    return Math.max(1000, rs.getInt(2) * 1000 - 1000);
                } else {
                    return -1;
                }
            }
        } catch (SQLException e) {
            return -1;
        }
    }

    private static Connection connectToDB() {
        try {
            Connection localConnection = ds.getConnection();
            Long timeout = getWaitTimeout(localConnection);
            if (timeout == -1) {
                System.out.println("[数据库信息] 无法读取超时时间，using " + connectionTimeOut + " instead.");
            }
            return localConnection;
        } catch (SQLException e) {
            log.error("[数据库信息] 数据库连接失败，请确认MYSQL服务是否开启，数据库名、账号、密码是否配置正确", e);
            throw new DatabaseException(e);
        }
    }

    private static void colse() {
        ds.close();
    }

    public static void closeAll() {
        ds.close();
    }

    public static DataBaseStatus TestConnection() {
        init();
        Connection localConnection = null;
        DataBaseStatus ret;
        try {
            localConnection = ds.getConnection();
            ret = DataBaseStatus.连接成功;
        } catch (Exception e) {
            Start.showMessage("连接数据库失败", "错误", 1);
            System.exit(0);
            ret = DataBaseStatus.连接失败;
        } finally {
            try {
                if (localConnection != null) {
                    localConnection.close();
                }
            } catch (SQLException e) {
                log.error("连接数据库失败", e);
            }
        }
        return ret;
    }

    public static synchronized Connection getConnection() {
        try {
            return ds.getConnection();
        } catch (SQLException e) {
            throw new DatabaseException(e);
        }
    }

    public enum DataBaseStatus {
        未初始化,
        连接成功,
        连接失败
    }

    public static class ConWrapper {

        private long lastAccessTime = 0;
        private Connection connection;

        public ConWrapper(Connection con) {
            this.connection = con;
        }

        public Connection getConnection() {
            if (expiredConnection()) {
                //System.out.println("[DB信息] 线程ID " + id + " 的SQL连接已经超时.重新连接...");
                try { // Assume that the connection is stale
                    connection.close();
                } catch (Throwable err) {
                    throw new DatabaseException("数据库错误", err);
                }
                this.connection = connectToDB();
            }
            lastAccessTime = System.currentTimeMillis(); // Record Access
            return this.connection;
        }

        /**
         * Returns whether this connection has expired
         *
         * @return
         */
        public boolean expiredConnection() {
            if (lastAccessTime == 0) {
                return false;
            }
            try {
                return System.currentTimeMillis() - lastAccessTime >= connectionTimeOut || connection.isClosed();
            } catch (Throwable ex) {
                return true;
            }
        }
    }
}
